package com.nightpiter.controller.algorithms;

import java.util.ArrayList;
import java.util.SortedMap;
import java.util.TreeMap;

import com.nightpiter.model.objects.Station;

/**
 * Vertex class for Dijkstra's Algorithm
 * 
 * Package protected
 * 
 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
 */
class Vertex {
	/**
	 * the station which is represented by this vertex
	 */
	private Station station;

	/**
	 * List of outgoing edges
	 */
	private ArrayList<Edge> edges = new ArrayList<Edge>();

	/**
	 * associates hops count with ways
	 */
	TreeMap<Integer, Way> passWayMap;

	/**
	 * shows if the vertex is already processed
	 */
	private int lastVisitPass;

	/**
	 * public constructor
	 * 
	 * @param station
	 */
	public Vertex(Station station) {
		this.station = station;
		this.reset();
	}

	/**
	 * add outgoing edge to the list
	 * 
	 * package protected
	 * 
	 * @param edge
	 *            the edge to add
	 */
	void addEdge(Edge edge) {
		this.edges.add(edge);
	}

	/**
	 * reset vertex data before new algorithm execution
	 */
	public void reset() {
		this.passWayMap = new TreeMap<Integer, Way>();
		this.lastVisitPass = -1;
	}

	/**
	 * examine the offered way and possibly accept it
	 * 
	 * @param edge
	 *            the offered way to reach the neighbor
	 * @param currentPassUnvisiteds
	 * @param nextPassUnvisiteds
	 * @return true if the way's applied and Vertex distance has decreased;
	 *         false otherwise
	 */
	public boolean tryWay(Edge edge, SortedVertexList currentPassUnvisiteds,
			SortedVertexList nextPassUnvisiteds) {

		boolean applied = false;
		boolean isNextPass = (edge.transferHops != 0);
		SortedVertexList unvisiteds;
		if (isNextPass) {
			unvisiteds = nextPassUnvisiteds;
		} else {
			unvisiteds = currentPassUnvisiteds;
		}

		Vertex vertex = edge.getFromVertex();
		int pass = vertex.lastVisitPass;

		Way vertexWay = vertex.passWayMap.get(pass);

		Way tryingWay = new Way(vertexWay, edge);
		int tryingHops = tryingWay.transferHops;
		Way currentWay = passWayMap.get(tryingHops);
		if (currentWay == null) {
			SortedMap<Integer, Way> lessHopsWays = passWayMap
					.headMap(tryingHops);
			if (lessHopsWays.isEmpty()
					|| tryingWay.compareByDistance(passWayMap.get(lessHopsWays
							.lastKey())) < 0) {
				if (!isNextPass) {
					Way nextPassWay = passWayMap.get(tryingHops + 1);
					if (nextPassWay != null
							&& tryingWay.compareByDistance(nextPassWay) < 0) {
						passWayMap.remove(nextPassWay);
						nextPassUnvisiteds.remove(this);
					}
				}
				passWayMap.put(tryingHops, tryingWay);
				unvisiteds.add(this);
				applied = true;
			}
		} else {
			if (tryingWay.compareByDistance(currentWay) < 0) {
				if (!isNextPass) {
					Way nextPassWay = passWayMap.get(tryingHops + 1);
					if (nextPassWay != null
							&& tryingWay.compareByDistance(nextPassWay) < 0) {
						passWayMap.remove(nextPassWay);
						nextPassUnvisiteds.remove(this);
					}
				}
				passWayMap.put(tryingHops, tryingWay);
				unvisiteds.add(this);
				applied = true;
			}
		}

		return applied;
	}

	/**
	 * examine the offered way and possibly accept it (without adding self to
	 * unvisiteds list)
	 * 
	 * @param edge
	 *            the offered way to reach the neighbor
	 * @param way
	 *            neighbor's way
	 * @return true if the way's applied and Vertex distance has decreased;
	 *         false otherwise
	 */
	public boolean tryWay(Edge edge, Way way) {

		boolean applied = false;
		boolean isNextPass = (edge.transferHops != 0);

		assert way != null;

		Way tryingWay = new Way(way, edge);
		int tryingHops = tryingWay.transferHops;
		Way currentWay = passWayMap.get(tryingHops);
		if (currentWay == null) {
			SortedMap<Integer, Way> lessHopsWays = passWayMap
					.headMap(tryingHops);
			if (lessHopsWays.isEmpty()
					|| tryingWay.compareByDistance(passWayMap.get(lessHopsWays
							.lastKey())) < 0) {
				if (!isNextPass) {
					Way nextPassWay = passWayMap.get(tryingHops + 1);
					if (nextPassWay != null
							&& tryingWay.compareByDistance(nextPassWay) < 0) {
						passWayMap.remove(nextPassWay);
					}
				}
				passWayMap.put(tryingHops, tryingWay);
				applied = true;
			}
		} else {
			if (tryingWay.compareByDistance(currentWay) < 0) {
				if (!isNextPass) {
					Way nextPassWay = passWayMap.get(tryingHops + 1);
					if (nextPassWay != null
							&& tryingWay.compareByDistance(nextPassWay) < 0) {
						passWayMap.remove(nextPassWay);
					}
				}
				passWayMap.put(tryingHops, tryingWay);
				applied = true;
			}
		}

		return applied;
	}

	/**
	 * @return the visited
	 */
	public boolean isVisited(int pass) {
		return (this.lastVisitPass >= pass);
	}

	public int getLastVisitedPass() {
		return lastVisitPass;
	}

	/**
	 * marks the vertex as visited
	 */
	public void setAsVisited(int pass) {
		this.lastVisitPass = pass;
	}

	/**
	 * marks the vertex as visited
	 */
	public void setAsStart() {
		passWayMap.put(0, new Way());
	}

	/**
	 * @return the station
	 */
	public Station getStation() {
		return station;
	}

	/**
	 * @return the edges
	 */
	public ArrayList<Edge> getEdges() {
		return edges;
	}

	public boolean hasWayEdges() {
		return !passWayMap.isEmpty();
	}

}
